package com.xhj.mybatis.session.defaults;

import com.xhj.mybatis.executor.Executor;
import com.xhj.mybatis.executor.statement.StatementHandler;
import com.xhj.mybatis.mapping.BoundSql;
import com.xhj.mybatis.mapping.Environment;
import com.xhj.mybatis.mapping.MappedStatement;
import com.xhj.mybatis.session.*;
import com.xhj.mybatis.transaction.Transaction;
import com.xhj.mybatis.transaction.TransactionFactory;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * DefaultSqlSessionFactory
 *
 * @author XJks
 * @description SqlSessionFactory接口的默认实现类
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    /**
     * 全局配置对象
     */
    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(null, false);
    }

    @Override
    public SqlSession openSession(boolean autoCommit) {
        return openSessionFromDataSource(null, autoCommit);
    }

    @Override
    public SqlSession openSession(TransactionIsolationLevel level) {
        return openSessionFromDataSource(level, false);
    }

    @Override
    public SqlSession openSession(Connection connection) {
        return openSessionFromConnection(connection);
    }

    /**
     * 通过 Configuration 配置的数据源打开一个新的 SqlSession 会话。
     * TODO:
     *  <pre>
     *  1.事务自动提交的配置调用链分析:
     *      1.1 {@link #openSession(boolean)}
     *      1.2 {@link #openSessionFromDataSource(TransactionIsolationLevel, boolean)}:
     *              (1) 该方法会执行 {@code tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit)}语句，创建事务对象 Transaction，接下来进入相关方法分析。
     *      1.3 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransactionFactory#newTransaction(javax.sql.DataSource, TransactionIsolationLevel, boolean)}
     *      1.4 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#JdbcTransaction(javax.sql.DataSource, TransactionIsolationLevel, boolean)}:
     *              (1) 最终会将 autoCommit 参数传递给 JdbcTransaction 的构造方法，赋值给成员变量 this.autoCommit。
     *              (2) 至此，最终完成事务对象的创建，完成自动提交配置的传递。
     *  2.事务自动提交的执行调用链分析:
     *      用户执行 SqlSession 的增删改查操作，最终会调用 {@link com.xhj.mybatis.executor.SimpleExecutor#doUpdate(MappedStatement ms, Object parameter)} 和 {@link com.xhj.mybatis.executor.SimpleExecutor#doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)} 方法，接下来深入分析。
     *      2.1 分析 {@link com.xhj.mybatis.executor.SimpleExecutor#doUpdate(MappedStatement ms, Object parameter)}方法:
     *              2.1.1 {@link com.xhj.mybatis.executor.SimpleExecutor#doUpdate(MappedStatement ms, Object parameter)}
     *                  (1) 该方法会执行 {@code stmt = prepareStatement(handler)} 语句，准备 SQL 语句，接下来进入相关方法分析。
     *              2.1.2 {@link com.xhj.mybatis.executor.SimpleExecutor#prepareStatement(StatementHandler handler)}
     *                  (1) 该方法会执行 {@code Connection connection = transaction.getConnection()} 语句，获取数据库连接，接下来进入相关方法分析。
     *              2.1.3 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#getConnection()}
     *                  (1) 该方法会调用 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#openConnection()} 方法，打开数据库连接，接下来进入相关方法分析。
     *              2.1.4 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#openConnection()}
     *                  (1) 该方法会执行 {@code setDesiredAutoCommit(autoCommit)} 语句，设置连接的自动提交属性，接下来进入相关方法分析。
     *              2.1.5 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#setDesiredAutoCommit(boolean desiredAutoCommit)}
     *                  (1) 该方法会执行 {@code connection.setAutoCommit(desiredAutoCommit)} 语句，最终将自动提交属性设置到数据库连接上，至此，完成自动提交配置的传递和执行。
     *      2.2 分析 {@link com.xhj.mybatis.executor.SimpleExecutor#doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)}方法:
     *              2.2.1 {@link com.xhj.mybatis.executor.SimpleExecutor#doUpdate(MappedStatement ms, Object parameter)}
     *                  (1) 该方法会执行 {@code Connection connection = transaction.getConnection()} 语句，获取数据库连接，接下来进入相关方法分析。
     *              2.1.2 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#getConnection()}
     *                  (1) 该方法会调用 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#openConnection()} 方法，打开数据库连接，接下来进入相关方法分析。
     *              2.1.3 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#openConnection()}
     *                  (1) 该方法会执行 {@code setDesiredAutoCommit(autoCommit)} 语句，设置连接的自动提交属性，接下来进入相关方法分析。
     *              2.1.4 {@link com.xhj.mybatis.transaction.jdbc.JdbcTransaction#setDesiredAutoCommit(boolean desiredAutoCommit)}
     *                  (1) 该方法会执行 {@code connection.setAutoCommit(desiredAutoCommit)} 语句，最终将自动提交属性设置到数据库连接上，至此，完成自动提交配置的传递和执行。
     *  <pre/>
     * @param level      事务隔离级别
     * @param autoCommit 事务是否自动提交
     * @return SqlSession 会话
     */
    private SqlSession openSessionFromDataSource(TransactionIsolationLevel level, boolean autoCommit) {
        // 声明事务对象，初始为 null
        Transaction tx = null;
        try {
            // 获取 MyBatis 的环境配置（包含数据源、事务工厂等信息）
            final Environment environment = configuration.getEnvironment();
            // 从环境中获取事务工厂（用于创建事务对象）
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            // 使用事务工厂创建一个新的事务，传入数据源、隔离级别和自动提交参数
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 基于事务创建一个新的执行器（Executor）
            final Executor executor = configuration.newExecutor(tx);
            // 创建并返回一个新的 DefaultSqlSession，会话对象，包含配置、执行器和自动提交设置
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // 如果上面过程出错，关闭事务（有可能已经获取了数据库连接）
            closeTransaction(tx);
            // 抛出包装后的异常，包含详细错误信息
            throw new RuntimeException("Error opening session.  Cause: " + e, e);
        }
    }

    /**
     * 通过已存在的数据库连接对象，打开一个新的 SqlSession 会话
     *
     * @param connection 数据库连接对象
     * @return SqlSession 会话
     */
    private SqlSession openSessionFromConnection(Connection connection) {
        try {
            // 声明一个变量，用于记录当前连接是否自动提交事务
            boolean autoCommit;
            try {
                // 尝试获取连接的自动提交属性
                autoCommit = connection.getAutoCommit();
            } catch (SQLException e) {
                // 如果获取自动提交属性时出错（比如驱动不支持），则默认设置为 true
                // 这样可以保证大多数数据库和驱动都能正常工作
                autoCommit = true;
            }
            // 获取 MyBatis 的环境配置（包含数据源、事务工厂等信息）
            final Environment environment = configuration.getEnvironment();
            // 从环境中获取事务工厂（用于创建事务对象）
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            // 使用事务工厂和传入的连接对象，创建一个新的事务对象
            final Transaction tx = transactionFactory.newTransaction(connection);
            // 基于事务和执行器类型，创建一个新的执行器（Executor）
            final Executor executor = configuration.newExecutor(tx);
            // 创建并返回一个新的 DefaultSqlSession，会话对象，包含配置、执行器和自动提交设置
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // 如果上面过程出错，抛出包装后的异常，包含详细错误信息
            throw new RuntimeException("Error opening session.  Cause: " + e, e);
        }
    }

    /**
     * 从 Environment 环境对象中获取事务工厂 TransactionFactory
     *
     * @param environment 环境对象
     * @return 事务工厂
     */
    private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
        /**
         * todo:
         *  1.如果 environment 为 null，或者 environment 没有配置事务工厂，源码这里存在使用 ManagedTransactionFactory 默认事务工厂的逻辑。
         *  2.这里直接默认 environment 配置了事务工厂，则直接返回该事务工厂
         */
        return environment.getTransactionFactory();
    }

    /**
     * 关闭事务对象，释放相关资源。
     * 如果事务对象为 null，则什么都不做。
     * 如果关闭过程中发生 SQLException 异常，则忽略该异常（因为更早的异常更重要）。
     *
     * @param tx 需要关闭的事务对象
     */
    private void closeTransaction(Transaction tx) {
        // 判断事务对象是否不为 null，避免空指针异常
        if (tx != null) {
            try {
                // 调用事务的 close 方法，关闭事务，释放数据库连接等资源
                tx.close();
            } catch (SQLException ignore) {
                // 如果关闭事务时发生 SQL 异常，这里选择忽略异常。
            }
        }
    }
}
